Para esta sesión, los datos puedes descargarlos en tu carpeta data desde este enlace.
Usemos de nuevo los datos de los innovadores en Seattle:
Como se ve, tenemos la matriz de adyacencia, la cual abriremos esta vez en R:
# limpiar memoria
rm(list = ls())
# abrir red
adjacency=read.csv(file.path('data','seattleTop_adjMx.csv'),row.names = 1) # la tabla tiene este nombre
matrix <- as.matrix(adjacency)
# abrir atributos
attributes=read.csv(file.path('data','seattleTop_attrTbl.csv')) # la tabla tiene este nombre
En R, es común usar igraph para análisis básicos de redes. Creamos primero la red usando la matriz de adyacencia:
library(igraph)
topsNet <- graph_from_adjacency_matrix(matrix, mode="directed",weighted = TRUE)
summary(topsNet)
## IGRAPH aeeddde DNW- 46 588 --
## + attr: name (v/c), weight (e/n)
Como se ve, igraph nos informa que se ha creado la red asignándole un código (irrelevante), que es una red dirigida (DNW, la W indica que ha creado pesos en los enlaces), con 46 nodos y 588 enlaces. Indica que tiene un atributo name para los vértices (nodos) de tipo cáracter (texto), y que los enlaces (edges) tienen un atributo weight de tipo numérico.
Como tenemos una tabla con más atributos para los nodos, podemos añadirlos fácilmente:
V(topsNet)$male <- attributes$male
V(topsNet)$popularity <- attributes$followers
# ahora
summary(topsNet)
## IGRAPH aeeddde DNW- 46 588 --
## + attr: name (v/c), male (v/n), popularity (v/n), weight (e/n)
Ya están los nuevos atributos para vértices, popularity y male, ambos de tipo numérico.
Exportemos esta red para que pueda ser leída por Gephi:
igraph::write_graph(topsNet,file.path('data',"topsNetFull.graphml"),
format = "graphml")
Recordemos que nuestra red de Seattletonians:
is_connected(topsNet,mode = c("strong"))
## [1] FALSE
count_components(topsNet, mode = c("strong"))
## [1] 4
g_comp=igraph::components(topsNet, mode = c("strong"))
g_comp
## $membership
## rachelerman mattmcilwain DaveParkerSEA toddbishop ashannstew
## 1 1 1 1 1
## LeslieFeinzaig akipman matt_oppy gilbert juliesandler
## 1 1 1 1 1
## BradSmi crashdev ShaunaCausey john_gabbert moniguzman
## 1 1 1 1 1
## mattmday Rich_Barton daryn lovelletters etzioni
## 1 1 1 1 1
## MissDestructo heatherredman danshapiro medinism KieranSnyder
## 1 1 1 1 1
## hadip RajSinghSeattle funcOfJoe kirbywinfield stevesi
## 1 1 1 1 1
## Ryanintheus sonalpmane SoGulley X2morrowknight jinman
## 1 1 1 3 1
## tarah Jenerationy lanctot Kristen_Hammy nhuntwalker
## 4 1 1 1 1
## eugenio_pace JenMsft PeterHamilton sarahstood mcolacurcio
## 2 1 1 1 1
## marybethlambert
## 1
##
## $csize
## [1] 43 1 1 1
##
## $no
## [1] 4
Guardemos la membresía a esos componentes como un atributo del nodo:
V(topsNet)$component=g_comp$membership
Vemos que uno de los componentes strongly connected es muy numeroso. Para efectos prácticos, quedémonos con ese:
# seleccionando
nodes_Subset=V(topsNet)[V(topsNet)$component==1]
# filtrando red
topsNet_giant=induced_subgraph(topsNet, nodes_Subset)
# ahora:
summary(topsNet_giant)
## IGRAPH 0175a96 DNW- 43 575 --
## + attr: name (v/c), male (v/n), popularity (v/n), component (v/n),
## | weight (e/n)
Exportemos esta sub red para que pueda ser leída por Gephi:
igraph::write_graph(topsNet_giant,file.path('data',"topsNet_giant.graphml"),
format = "graphml")
Veamos algunos valores importantes:
edge_density(topsNet_giant)
## [1] 0.3183832
farthest_vertices(topsNet_giant)
## $vertices
## + 2/43 vertices, named, from 0175a96:
## [1] akipman sonalpmane
##
## $distance
## [1] 4
reciprocity(topsNet_giant)
## [1] 0.6782609
¿Qué tanta homofilia hay en la red?
Hacia los más conectados:
assortativity_degree(topsNet_giant,directed = T)
## [1] -0.248864
assortativity(topsNet_giant, values=V(topsNet_giant)$male, directed = T)
## [1] 0.06411198
assortativity(topsNet_giant, values=V(topsNet_giant)$popularity, directed = T)
## [1] -0.01605838
Abramos topsNet_giant.graphml, y a continuación:
Un paso inicial es saber qué tan alto es el coeficiente de clusterización:
transitivity(as.undirected(topsNet_giant,mode='collapse'),type = "localaverage")
## [1] 0.645001
transitivity(as.undirected(topsNet_giant,mode='collapse'),type = "global")
## [1] 0.5666072
Aquí igraph ha implementado el algoritmo sólo para el caso no dirigido. Por lo que probablemente este valor sea mayor al caso dirigido. Con un valor mayor a 0.5, podemos asumir que se puede encontrar clusters.
Con el grafo actual (topsNet_giant.graphml), Calculemos el coeficiente de clusterización en Gephi y comparemos.
Para darle más soporte a nuestra búsqueda de comunidades, hay que explorar la presencia de cliques.El clique es un conjunto de nodos donde todos pueden conectarse con todos. Para este caso, usarameos la librería statnet.
Como nuestra red la creamos con igraph, hay que convertirlo a grafo de statnet:
topsNet_giant_net <- intergraph::asNetwork(topsNet_giant)
# ya es de statnet:
topsNet_giant_net
## Network attributes:
## vertices = 43
## directed = TRUE
## hyper = FALSE
## loops = FALSE
## multiple = FALSE
## bipartite = FALSE
## total edges= 575
## missing edges= 0
## non-missing edges= 575
##
## Vertex attribute names:
## component male popularity vertex.names
##
## Edge attribute names:
## weight
Nuestro propósito aquí es saber si hay cliques:
library(statnet)
census <- clique.census(topsNet_giant_net)
# aqui vemos
census$clique.count
## Agg rachelerman mattmcilwain DaveParkerSEA toddbishop ashannstew
## 1 0 0 0 0 0 0
## 2 10 0 2 0 0 0
## 3 17 2 0 2 2 0
## 4 13 4 2 1 12 0
## 5 9 2 0 0 8 1
## 6 12 6 0 3 8 1
## 7 6 4 0 1 6 2
## 8 2 2 0 2 2 0
## 9 1 1 0 1 1 0
## LeslieFeinzaig akipman matt_oppy gilbert juliesandler BradSmi crashdev
## 1 0 0 0 0 0 0 0
## 2 0 1 0 0 0 2 1
## 3 0 0 1 2 1 1 2
## 4 0 0 0 1 4 2 0
## 5 3 0 0 0 2 0 2
## 6 4 0 3 0 9 0 2
## 7 4 0 1 0 4 0 1
## 8 1 0 1 0 2 0 0
## 9 1 0 0 0 1 0 0
## ShaunaCausey john_gabbert moniguzman mattmday Rich_Barton daryn lovelletters
## 1 0 0 0 0 0 0 0
## 2 0 0 0 1 1 0 0
## 3 3 0 4 0 1 0 0
## 4 5 0 4 2 0 1 0
## 5 2 0 3 2 0 1 3
## 6 3 2 3 0 0 1 8
## 7 3 0 1 0 0 0 5
## 8 2 0 0 0 0 0 2
## 9 1 0 0 0 0 0 1
## etzioni MissDestructo heatherredman danshapiro medinism KieranSnyder hadip
## 1 0 0 0 0 0 0 0
## 2 1 0 0 0 2 1 0
## 3 0 0 2 4 0 2 3
## 4 2 2 0 0 0 2 0
## 5 0 2 5 0 0 1 0
## 6 1 5 5 0 0 1 0
## 7 1 3 4 0 0 0 0
## 8 1 0 1 0 0 0 0
## 9 0 1 1 0 0 0 0
## RajSinghSeattle funcOfJoe kirbywinfield stevesi Ryanintheus sonalpmane
## 1 0 0 0 0 0 0
## 2 2 1 1 1 0 0
## 3 2 0 2 5 2 0
## 4 0 0 0 0 1 0
## 5 0 0 1 3 1 0
## 6 0 0 2 2 1 2
## 7 0 0 0 1 0 0
## 8 0 0 0 0 0 0
## 9 0 0 0 0 0 0
## SoGulley jinman Jenerationy lanctot Kristen_Hammy nhuntwalker JenMsft
## 1 0 0 0 0 0 0 0
## 2 0 0 0 1 0 1 1
## 3 1 0 3 0 0 2 0
## 4 0 1 0 1 1 0 0
## 5 0 0 0 0 2 0 0
## 6 0 0 0 0 0 0 0
## 7 1 0 0 0 0 0 0
## 8 0 0 0 0 0 0 0
## 9 0 0 0 0 0 0 0
## PeterHamilton sarahstood mcolacurcio marybethlambert
## 1 0 0 0 0
## 2 0 0 0 0
## 3 1 0 0 1
## 4 1 2 1 0
## 5 0 0 1 0
## 6 0 0 0 0
## 7 0 0 0 0
## 8 0 0 0 0
## 9 0 0 0 0
La cantidad de filas nos dice los tamaños de los cliques máximales, es decir, un clique que no se puede ampliar incluyendo un nodo adyacente más (no es un subconjunto de un camarilla más grande):
census$clique.count[1,1]
## [1] 0
census$clique.count[2,1]
## [1] 10
census$clique.count[9,1]
## [1] 1
Si hay cliques, se sospecha la presencia de comunidades.
sort(edge_betweenness(topsNet_giant,directed = T),decreasing = T)[1:5]
## [1] 42.12500 41.80284 26.52518 26.29048 23.73203
l=layout_with_fr(topsNet_giant)
plot(topsNet_giant,layout=l,edge.arrow.size=1,vertex.label='',
edge.color=ifelse(edge_betweenness(topsNet_giant)>40,'red','grey90'))
El trabajo sería ir eliminando vertices hasta quedarse con la ‘mejor’ partición. Esto lo hace directamente el algoritmo the Girvan-Newman:
topsNet_giant_GN <- cluster_edge_betweenness(topsNet_giant, directed=T)
topsNet_giant_GN
## IGRAPH clustering edge betweenness, groups: 2, mod: 0.0032
## + groups:
## $`1`
## [1] "rachelerman" "mattmcilwain" "DaveParkerSEA" "toddbishop"
## [5] "ashannstew" "LeslieFeinzaig" "matt_oppy" "gilbert"
## [9] "juliesandler" "BradSmi" "crashdev" "ShaunaCausey"
## [13] "john_gabbert" "moniguzman" "mattmday" "Rich_Barton"
## [17] "daryn" "lovelletters" "etzioni" "MissDestructo"
## [21] "heatherredman" "danshapiro" "medinism" "KieranSnyder"
## [25] "hadip" "RajSinghSeattle" "funcOfJoe" "kirbywinfield"
## [29] "stevesi" "Ryanintheus" "sonalpmane" "SoGulley"
## [33] "jinman" "Jenerationy" "lanctot" "Kristen_Hammy"
## + ... omitted several groups/vertices
Se indica que ha encontrado sólo dos comunidades. Nótese que indica que este resultado es el que dió la ’mejor” partición: la mejor modularidad:
modularity(topsNet_giant_GN)
## [1] 0.003175803
El detalle de las dos comunidades lo vemos así:
communities(topsNet_giant_GN)
## $`1`
## [1] "rachelerman" "mattmcilwain" "DaveParkerSEA" "toddbishop"
## [5] "ashannstew" "LeslieFeinzaig" "matt_oppy" "gilbert"
## [9] "juliesandler" "BradSmi" "crashdev" "ShaunaCausey"
## [13] "john_gabbert" "moniguzman" "mattmday" "Rich_Barton"
## [17] "daryn" "lovelletters" "etzioni" "MissDestructo"
## [21] "heatherredman" "danshapiro" "medinism" "KieranSnyder"
## [25] "hadip" "RajSinghSeattle" "funcOfJoe" "kirbywinfield"
## [29] "stevesi" "Ryanintheus" "sonalpmane" "SoGulley"
## [33] "jinman" "Jenerationy" "lanctot" "Kristen_Hammy"
## [37] "nhuntwalker" "PeterHamilton" "sarahstood" "mcolacurcio"
## [41] "marybethlambert"
##
## $`2`
## [1] "akipman" "JenMsft"
La pertenecia a cada grupo podemos guardarla como un atributo:
V(topsNet_giant)$GNpartition=topsNet_giant_GN$membership
Veamos el resultado de manera gráfica:
l=layout_with_kk(topsNet_giant)
plot(topsNet_giant_GN,topsNet_giant,rescale=T, layout=l,vertex.label='',edge.arrow.size=.2)
topsNet_giant_LV=cluster_louvain(as.undirected(topsNet_giant,mode="mutual"))
topsNet_giant_LV
## IGRAPH clustering multi level, groups: 4, mod: 0.2
## + groups:
## $`1`
## [1] "rachelerman" "ashannstew" "matt_oppy" "gilbert"
## [5] "juliesandler" "crashdev" "john_gabbert" "mattmday"
## [9] "daryn" "danshapiro" "medinism" "KieranSnyder"
## [13] "Kristen_Hammy"
##
## $`2`
## [1] "mattmcilwain" "akipman" "BradSmi" "Rich_Barton"
## [5] "etzioni" "hadip" "RajSinghSeattle" "funcOfJoe"
##
## + ... omitted several groups/vertices
Veamos la modularidad obtenida:
modularity(topsNet_giant_LV)
## [1] 0.1993162
Y las comunidades:
communities(topsNet_giant_LV)
## $`1`
## [1] "rachelerman" "ashannstew" "matt_oppy" "gilbert"
## [5] "juliesandler" "crashdev" "john_gabbert" "mattmday"
## [9] "daryn" "danshapiro" "medinism" "KieranSnyder"
## [13] "Kristen_Hammy"
##
## $`2`
## [1] "mattmcilwain" "akipman" "BradSmi" "Rich_Barton"
## [5] "etzioni" "hadip" "RajSinghSeattle" "funcOfJoe"
##
## $`3`
## [1] "DaveParkerSEA" "toddbishop" "ShaunaCausey" "moniguzman"
## [5] "Ryanintheus" "SoGulley" "jinman" "Jenerationy"
## [9] "lanctot" "nhuntwalker" "PeterHamilton" "sarahstood"
## [13] "mcolacurcio" "marybethlambert"
##
## $`4`
## [1] "LeslieFeinzaig" "lovelletters" "MissDestructo" "heatherredman"
## [5] "kirbywinfield" "stevesi" "sonalpmane" "JenMsft"
La pertenecia a cada grupo podemos guardarla nuevamente como un atributo del nodo:
V(topsNet_giant)$LVpartition=topsNet_giant_LV$membership
Aqui de manera gráfica:
l=layout_with_kk(topsNet_giant)
plot(topsNet_giant_LV,topsNet_giant,layout=l,vertex.label='',edge.arrow.size=.2)
Con el grafo actual (topsNet_giant.graphml), calculemos comunidades con la función Modularidad de Gephi (usa la técnica Louvain) . Notemos las diferencias (particiones, modularidad, membresía).
Un tema clave es encontrar, en medio de todos los nodos, aquellos cuya posición y patrón de conexiones nos revele que tienen mejores condiciones para facilitar o restringir el flujo que se traslada por la red. Nótese en la Figura 7 la presencia de agujeros, y la emergencia de nodos que median el flujo entre subredes.
Por ejemplo, podemos calcular los tres nodos más claves siguiendo esa idea:
#install.packages("influenceR", dependencies = TRUE)
influenceR::keyplayer(topsNet_giant,3)
## + 3/43 vertices, named, from 0175a96:
## [1] toddbishop ShaunaCausey KieranSnyder
Alternativamente, podemos revelar quiénes tienen menos restricciones en la red para lo mismo:facilitar o restringir el flujo que se traslada por la red.
igraph::constraint(topsNet_giant)
## rachelerman mattmcilwain DaveParkerSEA toddbishop ashannstew
## 0.1159127 0.1433083 0.1145126 0.1013494 0.1394147
## LeslieFeinzaig akipman matt_oppy gilbert juliesandler
## 0.1430804 0.2765752 0.1314037 0.1393513 0.1156528
## BradSmi crashdev ShaunaCausey john_gabbert moniguzman
## 0.1502496 0.1227078 0.1123185 0.2234507 0.1195957
## mattmday Rich_Barton daryn lovelletters etzioni
## 0.1487804 0.1371157 0.1775320 0.1258692 0.1381820
## MissDestructo heatherredman danshapiro medinism KieranSnyder
## 0.1282831 0.1182542 0.1493675 0.1853486 0.1278971
## hadip RajSinghSeattle funcOfJoe kirbywinfield stevesi
## 0.1194759 0.1334484 0.1887772 0.1414997 0.1135172
## Ryanintheus sonalpmane SoGulley jinman Jenerationy
## 0.1431305 0.1661130 0.1287263 0.2464649 0.1454532
## lanctot Kristen_Hammy nhuntwalker JenMsft PeterHamilton
## 0.1622389 0.1506816 0.2307269 0.2212805 0.1784957
## sarahstood mcolacurcio marybethlambert
## 0.2040247 0.1691871 0.2861118
Así, sabiendo que hay agujeros estructurales, podemos animarnos a asignar roles de brokerage.
Pensemos en una red que tiene tres particiones A, B, y C, es decir, los nodos están en sólo una de ellas, podemos asignar los siguientes roles:
El Coordinador: Al que media la comunicacíon dentro de su propia partición, es decir, el coordinador aCD se ubica así: ai->aCD->aj, ai,aCD,aj pertenecen a A.
El Consultor: Al que media la comunicacíon dentro de otra partición, es decir, el consultor bCT se ubica así: ai->bCT->aj, ai,aj pertenecen a A, bCT pertenece a B.
El Portero: Al que permite que una partición ajena se comunique con su propia partición, es decir, el portero bPT se ubica así: ai->bPT->bj, ai pertenece a A, y bPT, y bj pertenecen a B.
El Representante: Al que se acerca a otra partición con información de su propia partición, es decir, el representante aRP se ubica así: ai->aRP->bj, ai, y aRP pertenecen a A, y bi pertenecen a B.
El Enlace (liasion): Al que media entre particiones diferentes, es decir, el liason aLI se ubica así: bi->aLI->cj, bi pertenece a B, cj pertenece a C, aLI pertenece a A.
La librería igraph no calcula estos roles, pero si se puede con la librería statnet. Primero, se debe convertir la red de un formato a otro:
#install.packages("intergraph")
topsNet_giant_statnet <- intergraph::asNetwork(topsNet_giant)
Ahora, veamos los roles:
library(statnet)
brokerage(topsNet_giant_statnet,
cl=get.vertex.attribute(topsNet_giant_statnet, "LVpartition"))$raw.nli
## w_I w_O b_IO b_OI b_O t
## rachelerman 55 7 92 88 73 315
## mattmcilwain 5 3 10 18 11 47
## DaveParkerSEA 23 9 81 52 70 235
## toddbishop 66 70 224 151 215 726
## ashannstew 5 0 9 16 18 48
## LeslieFeinzaig 4 12 28 23 29 96
## akipman 0 0 0 1 0 1
## matt_oppy 16 3 34 23 21 97
## gilbert 4 1 8 21 9 43
## juliesandler 47 12 96 90 88 333
## BradSmi 12 1 9 17 8 47
## crashdev 19 5 26 58 40 148
## ShaunaCausey 69 14 113 96 67 359
## john_gabbert 0 0 1 1 0 2
## moniguzman 34 13 87 58 46 238
## mattmday 14 1 18 11 9 53
## Rich_Barton 4 0 3 11 4 22
## daryn 5 0 8 3 0 16
## lovelletters 4 18 52 39 87 200
## etzioni 7 3 33 22 19 84
## MissDestructo 0 15 41 21 56 133
## heatherredman 4 46 67 52 125 294
## danshapiro 3 0 7 5 2 17
## medinism 3 0 0 3 0 6
## KieranSnyder 13 5 26 31 23 98
## hadip 3 5 11 21 22 62
## RajSinghSeattle 4 0 11 20 13 48
## funcOfJoe 1 0 2 1 0 4
## kirbywinfield 0 8 9 17 22 56
## stevesi 16 18 43 78 73 228
## Ryanintheus 15 4 23 10 0 52
## sonalpmane 0 0 11 2 2 15
## SoGulley 9 1 19 10 12 51
## jinman 0 0 1 0 0 1
## Jenerationy 7 0 20 1 1 29
## lanctot 0 0 7 3 3 13
## Kristen_Hammy 2 0 6 9 7 24
## nhuntwalker 2 0 4 3 2 11
## JenMsft 0 0 2 2 5 9
## PeterHamilton 2 0 8 1 2 13
## sarahstood 2 0 6 0 0 8
## mcolacurcio 1 0 3 4 4 12
## marybethlambert 0 0 0 1 0 1
Un poco de traducción:
El Coordinador: w_I
El Consultor: w_O
El Portero: b_IO
El Representante:b_OI
El Enlace (liasion): b_O
Es decir: rachelerman ocupa 35 veces rol de Coordinador, 15 rol de Consultor, 85 de Portero, 100 de Representante, y 80 liasion.
Creemos el archivo de enlaces (edgelist) desde R:
Puede descargar ese archivo desde la carpeta de datos en este momento.
Luego, desde Gephi, vayamos al Laboratorio de datos y descarguemos en CSV los atributos calculados por nodo, llame a ese archivo Seattle_giant_nodos.csv.
Antes de crearlo, cerciorese de sólo seleccionar dos columnas, la que tiene el nombre, y la que tiene la partición por modularidad (Louvain). Sobretodo, evite guardar ID y LABEL, tal como se indica en la Figura 9.
Ahora abrimos UCINET, y abrimos estos archivos desde el DL Editor:
Para calcular el constraint de los agujeros estructurales, sigamos los pasos indicados en las Figuras 12 y 13.
Para calcular los roles de brokerage, sigamos los pasos indicados en las Figuras 14 y 15.
Recuerda que el brokerage se ha calculado con
tres particiones, según Gephi, y que en R lo hicimos
con cuatro particiones.
Para este ejercicio trabaje con toda la red de los innovadores de Seattle (no sólo con el mayor componente).
Prepara tu respuesta con texto e imagenes de lo obtenido en un GoogleDoc.